package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConstant;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.gateway.properties.AuthProperties;
import com.atguigu.gmall.model.user.entity.UserInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;

@Component
@EnableConfigurationProperties(AuthProperties.class)
@Slf4j
public class UserAuthFilter implements GlobalFilter {

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    AuthProperties authProperties;

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取request
        ServerHttpRequest request = exchange.getRequest();
        //获取response对象
        ServerHttpResponse response = exchange.getResponse();
        //获取请求的uri
        URI url = request.getURI();
        String uri = request.getPath().toString();
        //获取当前请求的Ip
        String ipAddress = IpUtil.getGatwayIpAddress(request);

        log.info("uri = {}", uri);
        log.info("url = {}", url);
        //创建AntPathMatcher匹配器
        AntPathMatcher matcher = new AntPathMatcher();
        //先判断是不是黑名单用户
        long blockCount = authProperties.getIpBlockList().stream()
                .filter(ip -> matcher.match(ip, ipAddress))
                .count();
        if (blockCount > 0) {
            //说明是ip黑名单用户，直接构建错误返回
            String fail = JSONs.objecttoString(Result.build("", ResultCodeEnum.BLOCK_IPADDRESS));
            Mono<Void> mono = getVoidMono(response, fail);
            return mono;
        }
        //如果是静态文静直接放行
        long passCount = authProperties.getPassUrl().stream()
                //判断是否满足放行需要
                .filter(path -> matcher.match(path, uri))
                .count();
        if (passCount > 0) {
            //表示是静态资源，直接放行
            return chain.filter(exchange);
        }
        //判断是不是拒绝请求
        long denyCount = authProperties.getDenyUrl().stream()
                .filter(path -> matcher.match(path, uri))
                .count();
        if (denyCount > 0) {
            //表示是拒绝请求
            String fail = JSONs.objecttoString(Result.build("", ResultCodeEnum.PERMISSION));
            //获取拒绝的json数据
            Mono<Void> mono = getVoidMono(response, fail);
            return mono;
        }
        //判断是不是需要登录验证的请求
        long authCount = authProperties.getAuthUrl().stream()
                //判断是否满足放行需要
                .filter(path -> matcher.match(path, uri))
                .count();
        String loginUrl = authProperties.getLoginUrl() + "?originUrl=" + url;

        if (authCount > 0) {
            //表示是需要登录验证的请求
            //从cookie或者请求头里面拿到token
            String token = getToken(request);
            if (token == null) {
                //如果没有token直接重定向到登录界面
                Mono<Void> mono = sendRedict(response, loginUrl);
                return mono;
            }
            //能到这里说明token有值
            UserInfo userInfo = getUserInfoStr(token);
            if (userInfo == null || !ipAddress.equals(userInfo.getIpAddress())) {
                //如果没有token错误，直接重定向到登录界面
                Mono<Void> mono = sendRedict(response, loginUrl);
                return mono;
            }
        }

        // 如果登录了：以前 user_id 透传
        // 如果登录了：token 透传

        //4、 普通请求：预检查。 你没带令牌往下放，带了令牌，顺便验一下。
        String token = getToken(request);
        if (StringUtils.hasText(token)) {
            UserInfo userInfo = getUserInfoStr(token);
            if (userInfo == null || !ipAddress.equals(userInfo.getIpAddress())) {
                //如果没有token错误，直接重定向到登录界面
                return sendRedict(response, loginUrl);
            }
        }

        //正确放行
        return chain.filter(exchange);
    }

    private UserInfo getUserInfoStr(String token) {
        String userStr = stringRedisTemplate.opsForValue().get(RedisConstant.REDIS_USER_INFO_PREFIX + token );
        if (!StringUtils.hasText(userStr)) {
            return null;
        }
        return JSONs.stringToObj(userStr,UserInfo.class);
    }

    private Mono<Void> sendRedict(ServerHttpResponse response, String location) throws URISyntaxException {
        //重定向的底层原理就是修改状态码为302，并且改请求头里面的Location
        response.setStatusCode(HttpStatus.FOUND);
        //设置地址
        response.getHeaders().setLocation(new URI(location));
        //删除前端的cookie
        //命令浏览器，把以前 Cookie 中保存的 错误 token 删除掉；  给浏览器发一个同名cookie，且立即失效，浏览器按照服务器的命令保存一个同名cookie，且用服务器指定的过期时间
        // cookie的age：
        // 1、正数： 指定时间后，cookie过期
        // 2、0：   cookie立即过期，命令浏览器立即删除
        // 3、负数： Cookie 会在浏览器关闭以后删除；  会话cookie
        ResponseCookie cookie = ResponseCookie.from("token", "")
                .path("/")
                .maxAge(0)
                .domain(".gmall.com")
                .build();

        response.addCookie(cookie);

        Mono<Void> mono = response.setComplete();
        return mono;
    }

    private String getToken(ServerHttpRequest request) {
        String token = "";
        HttpCookie tokenCook = request.getCookies().getFirst("token");
        if (tokenCook != null) {
            token = request.getCookies().getFirst("token").getValue();
        } else {
            token = request.getHeaders().getFirst("Token");
        }
        if (!StringUtils.hasText(token)) {
            return null;
        }
        return token;
    }

    private static Mono<Void> getVoidMono(ServerHttpResponse response, String fail) {
        //错误信息封装成DaBuffer对象
        DefaultDataBuffer wrap = new DefaultDataBufferFactory().wrap(fail.getBytes());
        //封装成发布者
        Mono<DefaultDataBuffer> just = Mono.just(wrap);
        //设置响应给前端的数据格式
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        //将数据写出
        Mono<Void> mono = response.writeWith(just);
        return mono;
    }

}
